// Triangle.cpp
// Our first OpenGL program that will just draw a triangle on the screen.
#define WIN32

#include <GLTools.h>            // OpenGL toolkit
#include <GLFrame.h>
#include <GLFrustum.h>
#include <GLBatch.h>
#include <GLMatrixStack.h>
#include <GLGeometryTransform.h>
#ifdef __APPLE__
#include <glut/glut.h>          // OS X version of GLUT
#else
#define FREEGLUT_STATIC
#include <GL/glut.h>            // Windows FreeGlut equivalent
#endif
#include <iostream>
#include <vector>
#include <StopWatch.h>



bool stopRotate=false;
float rotAngle=0.f;
//GLFrame frame;
GLFrame cameraFrame;
GLFrustum frustum;
GLuint shader;
CStopWatch timer;

int MVPMatrixLocation;
int MVMatrixLocation;
int MNMatrixLocation;

int shaderPositionLocation;
int shaderColorLocation;
int shaderAngleLocation;
int shaderAttenuation0Location;
int shaderAttenuation1Location;
int shaderAttenuation2Location;

int shaderMaterialAmbientLocation;
int shaderMaterialSpecularLocation;
int shaderMaterialDiffuseLocation;
int shaderMaterialSpecularExponentLocation;


int n_vertices=0;
int n_faces=0;


///////////////////////////////////////////////////////////////////////////////
// Window has changed size, or has just been created. In either case, we need
// to use the window dimensions to set the viewport and the projection matrix.

void ChangeSize(int w, int h) {
    glViewport(0, 0, w, h);
	frustum.SetPerspective(50.0f, w/h, 0.1f, 1000.0f);
}


void SetUpFrame(GLFrame &frame,const M3DVector3f origin,
				const M3DVector3f forward,
				const M3DVector3f up) {
					frame.SetOrigin(origin);
					frame.SetForwardVector(forward);
M3DVector3f side,oUp;
m3dCrossProduct3(side,forward,up);
m3dCrossProduct3(oUp,side,forward);
frame.SetUpVector(oUp);
frame.Normalize();

}

void LookAt(GLFrame &frame, const M3DVector3f eye,
        const M3DVector3f at,
        const M3DVector3f up) {
    M3DVector3f forward;
    m3dSubtractVectors3(forward, at, eye);
    SetUpFrame(frame, eye, forward, up);
}


void TriangleFace(M3DVector3f a, M3DVector3f b, M3DVector3f c) {
      M3DVector3f normal, bMa, cMa;
      m3dSubtractVectors3(bMa, b, a);
      m3dSubtractVectors3(cMa, c, a);
      m3dCrossProduct3(normal, bMa, cMa);
      m3dNormalizeVector3(normal);
      glVertexAttrib3fv(GLT_ATTRIBUTE_NORMAL, normal);
	//  glGetAttribute3fv(GLT_ATTRIBUTE_NORMAL,normal);
      glVertex3fv(a);
      glVertex3fv(b);
      glVertex3fv(c);
}

void myTriangleFace(float f11,float f12,float f13,
	float f21,float f22,float f23,
	float f31,float f32,float f33) {
	M3DVector3f face1,face2,face3;
	face1[0]=f11;
	face1[1]=f12;
	face1[2]=f13;

	face2[0]=f21;
	face2[1]=f22;
	face2[2]=f23;

	face3[0]=f31;
	face3[1]=f32;
	face3[2]=f33;
	TriangleFace(face1,face2,face3);
}





float ico_vertices[3 * 12] = {
      0., 0., -0.9510565162951536,
      0., 0., 0.9510565162951536,
      -0.85065080835204, 0., -0.42532540417601994,
      0.85065080835204, 0., 0.42532540417601994,
      0.6881909602355868, -0.5, -0.42532540417601994,
      0.6881909602355868, 0.5, -0.42532540417601994,
      -0.6881909602355868, -0.5, 0.42532540417601994,
      -0.6881909602355868, 0.5, 0.42532540417601994,
      -0.2628655560595668, -0.8090169943749475, -0.42532540417601994,
      -0.2628655560595668, 0.8090169943749475, -0.42532540417601994,
      0.2628655560595668, -0.8090169943749475, 0.42532540417601994,
      0.2628655560595668, 0.8090169943749475, 0.42532540417601994
      };
int ico_faces[3*20]={
      1 ,			 11 ,			 7 ,
      1 ,			 7 ,			 6 ,
      1 ,			 6 ,			 10 ,
      1 ,			 10 ,			 3 ,
      1 ,			 3 ,			 11 ,
      4 ,			 8 ,			 0 ,
      5 ,			 4 ,			 0 ,
      9 ,			 5 ,			 0 ,
      2 ,			 9 ,			 0 ,
      8 ,			 2 ,			 0 ,
      11 ,			 9 ,			 7 ,
      7 ,			 2 ,			 6 ,
      6 ,			 8 ,			 10 ,
      10 ,			 4 ,			 3 ,
      3 ,			 5 ,			 11 ,
      4 ,			 10 ,			 8 ,
      5 ,			 3 ,			 4 ,
      9 ,			 11 ,			 5 ,
      2 ,			 7 ,			 9 ,
      8 ,			 6 ,			 2 };
 



void drawTriangles(int n_faces, float *vertices, int *faces) {
for (int i = 0; i < n_faces; i++) {
glBegin(GL_TRIANGLES);
TriangleFace(vertices + 3 * faces[3 * i], vertices + 3 * faces[3 * i + 1], vertices + 3 * faces[3 * i + 2]);
glEnd();
}
}
 
void drawSmoothTriangles(int n_faces, float *vertices, int *faces) {
      M3DVector3f normal;
      for (int i = 0; i < n_faces; i++) {
      glBegin(GL_TRIANGLES);
      for(int j=0;j<3;++j) {
      m3dCopyVector3(normal,vertices+3*faces[i*3+j]);
      m3dNormalizeVector3(normal);
      glVertexAttrib3fv(GLT_ATTRIBUTE_NORMAL, normal);
      glVertex3fv(vertices+3*faces[i*3+j]);
      
      }
      glEnd();
      }
      }
 
///////////////////////////////////////////////////////////////////////////////
// This function does any needed initialization on the rendering context.
// This is the first opportunity to do any OpenGL related tasks.

void SetupRC() {
	

    // Blue background
    glClearColor(0.0f, 0.0f, 1.0f, 1.0f);

    shader = gltLoadShaderPairWithAttributes("vshader.vp", "fshader.fp",
            2, GLT_ATTRIBUTE_VERTEX, "vVertex",  GLT_ATTRIBUTE_NORMAL, "vNormal");
    fprintf(stdout, "GLT_ATTRIBUTE_VERTEX : %d\nGLT_ATTRIBUTE_NORMAL : %d \n",
            GLT_ATTRIBUTE_VERTEX, GLT_ATTRIBUTE_COLOR);


	MVPMatrixLocation=glGetUniformLocation(shader,"MVPMatrix");
	if(MVPMatrixLocation==-1)
	{
		fprintf(stderr,"uniform MVPMatrix could not be found\n");
	}

	MVMatrixLocation=glGetUniformLocation(shader,"MVMatrix");
	if(MVMatrixLocation==-1)
	{
		fprintf(stderr,"uniform MVMatrix could not be found\n");
	}

	MNMatrixLocation=glGetUniformLocation(shader,"normalMatrix");
	if(MNMatrixLocation==-1)
	{
		fprintf(stderr,"uniform normalMatrix could not be found\n");
	}


	if((shaderPositionLocation = glGetUniformLocation(shader, "light1.position"))==-1)
		fprintf(stderr,"uniform light1.position could not be found\n");
	if((shaderColorLocation = glGetUniformLocation(shader, "light1.color"))==-1)
		fprintf(stderr,"uniform light1.color could not be found\n");
	if((shaderAngleLocation = glGetUniformLocation(shader, "light1.angle"))==-1)
		fprintf(stderr,"uniform light1.angle could not be found\n");
	if((shaderAttenuation0Location = glGetUniformLocation(shader, "light1.attenuation0"))==-1)
		fprintf(stderr,"uniform light1.attenuation0 could not be found\n");
	if((shaderAttenuation1Location = glGetUniformLocation(shader, "light1.attenuation1"))==-1)
		fprintf(stderr,"uniform light1.attenuation1 could not be found\n");
	if((shaderAttenuation2Location = glGetUniformLocation(shader, "light1.attenuation2"))==-1)
		fprintf(stderr,"uniform light1.attenuation2 could not be found\n");



	if((shaderMaterialAmbientLocation=glGetUniformLocation(shader, "material.ambientColor"))==-1)
		fprintf(stderr,"uniform material.ambientColor could not be found\n");
	if((shaderMaterialDiffuseLocation=glGetUniformLocation(shader, "material.diffuseColor"))==-1)
		fprintf(stderr,"uniform material.diffuseColor could not be found\n");
	if((shaderMaterialSpecularLocation=glGetUniformLocation(shader, "material.specularColor"))==-1)
		fprintf(stderr,"uniform material.specularColor could not be found\n");
	if((shaderMaterialSpecularExponentLocation=glGetUniformLocation(shader, "material.specularExponent"))==-1)
		fprintf(stderr,"uniform material.specularExponent could not be found\n");




	FILE *fvertices=fopen("geode_vertices.dat","r");
   if(fvertices==NULL) {
   fprintf(stderr,"cannot open vertices file for reading\n");
   exit(-1);
   }
   char line[120];
   
   std::vector<float> vertices;

   while(fgets(line,120,fvertices)!=NULL) {
   float x,y,z;
   double norm;
   sscanf(line,"%f %f %f",&x,&y,&z);
  
   norm=x*x+y*y+z*z;
   norm=sqrt(norm);
   n_vertices++;
   vertices.push_back(x);
   vertices.push_back(y);
   vertices.push_back(z);
   vertices.push_back(1.0f);
   vertices.push_back(x/norm);
   vertices.push_back(y/norm);
   vertices.push_back(z/norm);
   }
 fprintf(stderr,"nv = %u %u\n",n_vertices,vertices.size());


//FACE

	FILE *ffaces=fopen("geode_faces.dat","r");
   if(ffaces==NULL) {
   fprintf(stderr,"cannot open faces file for reading\n");
   exit(-1);
   }

   std::vector<GLuint> faces;
   while(fgets(line,120,ffaces)!=NULL) {
   GLuint  i,j,k;
   
   if(3!=sscanf(line,"%u %u %u",&i,&j,&k)){
   fprintf(stderr,"error reading faces\n"); 
   exit(-1);
   }
   //fprintf(stderr,"%u %u %u\n",i-1,j-1,k-1);
   n_faces++;
   faces.push_back(i-1);
   faces.push_back(j-1);
   faces.push_back(k-1);
   
   }
 	fprintf(stderr,"nf = %u\n",n_faces);


	GLuint vertex_buffer,faces_buffer;
	glGenBuffers(1,&vertex_buffer);
	glBindBuffer(GL_ARRAY_BUFFER, vertex_buffer);


   glBufferData(GL_ARRAY_BUFFER,n_vertices*sizeof(float)*7,&vertices[0],GL_STATIC_DRAW);
   if(glGetError()!=GL_NO_ERROR) {
   	fprintf(stderr,"error copying vertices\n");
   }

   glVertexAttribPointer(GLT_ATTRIBUTE_VERTEX,4,GL_FLOAT,GL_FALSE,sizeof(float)*7,(const GLvoid *)0);

	glVertexAttribPointer(GLT_ATTRIBUTE_NORMAL,3,GL_FLOAT,GL_FALSE,
	   sizeof(float)*7,(const GLvoid *)(4*sizeof(float)) );

	glEnableVertexAttribArray(GLT_ATTRIBUTE_VERTEX);
	glEnableVertexAttribArray(GLT_ATTRIBUTE_NORMAL);

	

	glGenBuffers(1,&faces_buffer);
	 if(glGetError()!=GL_NO_ERROR) {
	 fprintf(stderr,"faces_buffer invalid\n");
	 }

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER,faces_buffer);


	glBufferData(GL_ELEMENT_ARRAY_BUFFER,n_faces*sizeof(GLuint)*3,&faces[0],GL_STATIC_DRAW);
	 if(glGetError()!=GL_NO_ERROR) {
	 fprintf(stderr,"error copying faces\n");
	 }






	M3DVector3f eye = {3.0f, 3.0f, 10.0f};
	M3DVector3f at = {0.0f, 0.0f, 0.0f};
	M3DVector3f up = {0.0f, 0.0f, 1.0f};
	LookAt(cameraFrame, eye, at, up);


	glFrontFace(GL_CW);
	glEnable( GL_DEPTH_TEST );
	//glEnable( GL_CULL_FACE );

	glEnable( GL_BLEND );
	glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
    glUseProgram(shader);


}
void rysujTrojkat()
{
    glBegin(GL_TRIANGLES);
	static float scale =1.f;
	/////////podstawa
	glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 0.0, 1.0, 0.0);
	myTriangleFace(scale * -0.5f,scale * -0.5f,scale *.0f,scale *-0.5f,scale * 0.5f, scale *0.0,scale *0.5f,scale * -0.5f,scale * .0f);
	myTriangleFace(scale * 0.5f,scale * 0.5f,scale *.0f,scale *-0.5f,scale * 0.5f, scale *0.0,scale *0.5f,scale * -0.5f,scale * .0f);
	//// koniec podstawa
	///gorna
	glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 1.0, 0.0, 0.0);
	myTriangleFace(scale * 0.5f,scale * 0.5f,scale *.0f,scale *-0.5f,scale * 0.5f, scale *0.0,scale *0.f,scale * 0.f,scale * 2.0f);


	///prawa
	glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 0.0, 0.5, .5);
	myTriangleFace(scale * 0.5f,scale * 0.5f,scale *.0f,scale *0.f,scale * 0.f,scale * 2.0f,scale *0.5f,scale * -0.5f, scale *0.0);

	///dolna
	glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 0.5, 0.5, .5);
	myTriangleFace(scale * -0.5f,scale * -0.5f,scale *.0f,scale *0.5f,scale * -0.5f, scale *0.0,scale *0.f,scale * 0.f,scale * 2.0f);


	///lewa
	glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 1, 1, .5);
	myTriangleFace(scale * -0.5f,scale * -0.5f,scale *.0f,scale *0.f,scale * 0.f,scale * 2.0f,scale *-0.5f,scale * 0.5f, scale *0.0);
    glEnd();
}


void rysujSiatke()
{

    glBegin(GL_LINES);

	static float scale =.5f;
	
	for(int i=0;i<11;++i)
	{
		glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 0.0, 1.0, 0.0);
		glVertex3f((scale * (-10.f)) + i ,scale *10.f,scale *.0f);
		glVertex3f((scale * -10.f) + i ,scale * (-10.f),scale *.0f);
	}

	for(int i=0;i<11;++i)
	{
		glVertexAttrib3f(GLT_ATTRIBUTE_COLOR, 0.0, 1.0, 0.0);
		glVertex3f(scale *10.f,(scale * (-10.f)) + i ,scale *.0f);
		glVertex3f(scale * (-10.f),(scale * -10.f) + i ,scale *.0f);
	}


    glEnd();
}






///////////////////////////////////////////////////////////////////////////////
// Called to draw scene

//material
float ambientColor[3]={0.2f, 0.2f, 0.2f};
float diffuseColor[3]={.0f, 0.0f, 1.0};
float specularColor[3]={1.0f, 1.0f, 1.0f};
float specularExponent=9.f;
	
//light
float position[3]={-1.f,-10.f,10.f};
float color[3]={1.f,1.f,1.0f};
float angle=90.f;
float attenuation0=0.01f;
float attenuation1=0.01f;
float attenuation2=0.01f;


void RenderScene(void) {
    // Clear the window with current clearing color
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);


	glUniform3fv(shaderMaterialAmbientLocation, 1, ambientColor);
	glUniform3fv(shaderMaterialDiffuseLocation, 1, diffuseColor);
	glUniform3fv(shaderMaterialSpecularLocation, 1, specularColor);
	glUniform1f(shaderMaterialSpecularExponentLocation, specularExponent);
	
	//cameraFrame.GetOrigin(position);
	
	glUniform3fv(shaderPositionLocation, 1, position);
	glUniform3fv(shaderColorLocation, 1, color);
	glUniform1f(shaderAngleLocation, angle);
	glUniform1f(shaderAttenuation0Location, attenuation0);
	glUniform1f(shaderAttenuation1Location, attenuation1);
	glUniform1f(shaderAttenuation2Location, attenuation2);


	GLMatrixStack modelView;
	GLMatrixStack projection;
	GLGeometryTransform geometryPipeline;
	geometryPipeline.SetMatrixStacks(modelView,projection);

	
	projection.LoadMatrix(frustum.GetProjectionMatrix());
	modelView.PushMatrix();
	M3DMatrix44f mCamera;
	cameraFrame.GetCameraMatrix(mCamera);
	modelView.LoadMatrix(mCamera);
	modelView.PushMatrix();

	
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	rysujSiatke();

	modelView.PushMatrix();
	modelView.Translate(0.0f,0.0f,-1.f);
	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	rysujTrojkat();
	modelView.PopMatrix();




	modelView.PushMatrix();
	modelView.Translate(0.0f,4.0f,0.f);
	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	rysujTrojkat();
	modelView.PopMatrix();


	modelView.PushMatrix();
	modelView.Translate(3.0f,-3.0f,0.f);
	modelView.Scale(2.0f,2.0f,2.f);
	modelView.Rotate(45,0,0,1);
	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	rysujTrojkat();
	modelView.PopMatrix();


	modelView.PushMatrix();
	modelView.Translate(3.0f,3.0f,0.f);
	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	drawTriangles(20, ico_vertices,ico_faces);
	modelView.PopMatrix();

	modelView.PushMatrix();
	modelView.Translate(0.0f,0.0f,7.f);
	modelView.Rotate(90+rotAngle,1,1,1);
	modelView.Scale(2.0f,2.5f,2.5f);
	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	drawSmoothTriangles(20, ico_vertices,ico_faces);
	modelView.PopMatrix();




	modelView.PushMatrix();
	modelView.Translate(-4.0f,0.0f,4.f);
	modelView.Scale(2.0f,2.0f,2.0f);

	glUniformMatrix4fv(MVMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewMatrix());
	glUniformMatrix3fv(MNMatrixLocation,1,GL_FALSE,geometryPipeline.GetNormalMatrix());
	glUniformMatrix4fv(MVPMatrixLocation,1,GL_FALSE,geometryPipeline.GetModelViewProjectionMatrix());
	glDrawElements(GL_TRIANGLES,3*n_faces,GL_UNSIGNED_INT,0);

	modelView.PopMatrix();



    // Perform the buffer swap to display back buffer
    glutSwapBuffers();
	rotAngle+=5.f;
	if(!stopRotate){
	M3DVector3f at={0,0,0};
	M3DVector3f up={0,0,1};
	M3DVector3f eye;
	float angle = timer.GetElapsedSeconds()*3.14159f /3.f;
	eye[0]=16.f*cos(angle);
	eye[1]=40.0f*sin(angle);
	eye[2]=20.0f; 
	LookAt(cameraFrame,eye,at,up);
	}
glutPostRedisplay();
}

void mouseFunc(int button, int state,int x, int y)
{
	if(state==GLUT_DOWN)
		stopRotate=!stopRotate;
}
///////////////////////////////////////////////////////////////////////////////
// Main entry point for GLUT based programs

int main(int argc, char* argv[]) {
  

    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_DOUBLE | GLUT_RGBA | GLUT_DEPTH | GLUT_STENCIL);
    glutInitWindowSize(800, 800);
    glutCreateWindow("Triangle");
    glutReshapeFunc(ChangeSize);
    glutDisplayFunc(RenderScene);
	glutMouseFunc(mouseFunc);
    GLenum err = glewInit();
    if (GLEW_OK != err) {
        fprintf(stderr, "GLEW Error: %s\n", glewGetErrorString(err));
        return 1;
    }

    SetupRC();

    glutMainLoop();
    return 0;
}
